package cn.lg.soar.database.base.service.impl;

import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.database.base.entity.INumberTreeEntity;
import cn.lg.soar.database.base.enums.TreePositionRelationEnum;
import cn.lg.soar.database.base.service.INumberTreeService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Objects;

/**
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public abstract class NumberTreeServiceImpl<M extends BaseMapper<T>, T extends INumberTreeEntity> extends BaseServiceImpl<M, T> implements INumberTreeService<T> {

    private static final String LEFT_PLUS_SQL = INumberTreeEntity.LEFT_NO + " = " +  INumberTreeEntity.LEFT_NO + " + ";
    private static final String RIGHT_PLUS_SQL = INumberTreeEntity.RIGHT_NO + " = " +  INumberTreeEntity.RIGHT_NO + " + ";

    private static final String LEFT_MINUS_SQL = INumberTreeEntity.LEFT_NO + " = " +  INumberTreeEntity.LEFT_NO + " - ";
    private static final String RIGHT_MINUS_SQL = INumberTreeEntity.RIGHT_NO + " = " +  INumberTreeEntity.RIGHT_NO + " - ";

    @Resource
    private DataSourceTransactionManager transactionManager;

    @Override
    public boolean save(T entity) {
        Serializable parentId = entity.getParentId();
        if (parentId == null || "0".equals(parentId.toString())) {
            // 无父结点的往末尾插入，对既有数据无影响
            T first = getFirst(
                    Wrappers.<T>lambdaQuery(getEntityClass())
                            .select(T::getRightNo)
                            .orderByDesc(T::getRightNo)
            );
            if (first == null) {
                entity.setLeftNo(0);
                entity.setRightNo(1);
            } else {
                int rightNo = first.getRightNo();
                entity.setLeftNo(rightNo + 1);
                entity.setRightNo(rightNo + 2);
            }
            return super.save(entity);
        }
        // 存在父节点的须计算处理既有数据的左右值
        // 1。查询父节点右值
        T parent = getById(parentId);
        int parentRightNo = parent.getRightNo();
        AssertUtil.notNull(parent, "父节点不存在");
        int prevNo = parentRightNo - 1;
        entity.setLeftNo(parentRightNo);
        entity.setRightNo(parentRightNo + 1);
        // 更新左值(右移)
        LambdaUpdateWrapper<T> updateLeftWrapper = Wrappers.<T>lambdaUpdate(getEntityClass())
                .setSql(LEFT_PLUS_SQL + 2)
                .gt(T::getLeftNo, prevNo);
        // 更新右值(右移)
        LambdaUpdateWrapper<T> updateRightWrapper = Wrappers.<T>lambdaUpdate(getEntityClass())
                .setSql(RIGHT_PLUS_SQL + 2)
                .gt(T::getRightNo, prevNo);
        // 手动开启事务
        DefaultTransactionDefinition transDefinition = new DefaultTransactionDefinition();
        TransactionStatus transStatus = transactionManager.getTransaction(transDefinition);
        try {
            // 先腾出位置，可能没有左值操作，所有这里不判断是否成功
            update(updateLeftWrapper);
            AssertUtil.isTrue(update(updateRightWrapper), "右值更新失败");
            // 插入位置
            AssertUtil.isTrue(super.save(entity), "新增失败");
            // 提交事务
            transactionManager.commit(transStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transStatus);
            throw e;
        }
        return true;
    }

    @Override
    public boolean updateById(T entity) {
        Serializable parentId = entity.getParentId();
        if (parentId == null || Objects.equals(parentId, 0L)) {
            return super.updateById(entity);
        }
        Serializable id = entity.primaryKey();
        T t = getById(id);
        if (parentId.equals(t.getParentId())) {
            return super.updateById(entity);
        }
        // 手动开启事务
        DefaultTransactionDefinition transDefinition = new DefaultTransactionDefinition();
        TransactionStatus transStatus = transactionManager.getTransaction(transDefinition);
        try {
            move(id, parentId, TreePositionRelationEnum.children);
            // 插入位置
            AssertUtil.isTrue(super.updateById(entity), "更新失败");
            // 提交事务
            transactionManager.commit(transStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transStatus);
            throw e;
        }
        return true;
    }

    @Override
    public boolean removeById(Serializable id) {
        T entity = getById(id);
        if (entity == null) {
            return true;
        }
        int leftNo = entity.getLeftNo();
        int rightNo = entity.getRightNo();
        int moveValue = rightNo - leftNo + 1;
        // 删除节点及其子节点
        LambdaQueryWrapper<T> removeWrapper = Wrappers.<T>lambdaQuery(getEntityClass())
                .ge(T::getLeftNo, leftNo)
                .le(T::getRightNo, rightNo);
        // 更新左值(左移)
        LambdaUpdateWrapper<T> updateLeftWrapper = Wrappers.<T>lambdaUpdate(getEntityClass())
                .setSql(LEFT_MINUS_SQL + moveValue)
                .gt(T::getLeftNo, rightNo);
        // 更新右值(左移)
        LambdaUpdateWrapper<T> updateRightWrapper = Wrappers.<T>lambdaUpdate(getEntityClass())
                .setSql(RIGHT_MINUS_SQL + moveValue)
                .gt(T::getRightNo, rightNo);
        // 手动开启事务
        DefaultTransactionDefinition transDefinition = new DefaultTransactionDefinition();
        TransactionStatus transStatus = transactionManager.getTransaction(transDefinition);
        try {
            // 先删除腾出位置
            AssertUtil.isTrue(remove(removeWrapper), "删除失败");
            // 移动节点填满位置
            update(updateLeftWrapper);
            update(updateRightWrapper);
            // 提交事务
            transactionManager.commit(transStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transStatus);
            throw e;
        }
        return true;
    }

    /**
     * 批量删除
     * @param list 主键ID或实体列表
     * @return
     */
    @Transactional(rollbackFor = RuntimeException.class)
    public boolean removeByIds(Collection<?> list) {
        for (Object o : list) {
            removeById((Serializable) o);
        }
        return true;
    }

    @Override
    public void move(Serializable id, Serializable targetId, TreePositionRelationEnum relation) {
        T entity = getById(id);
        AssertUtil.notNull(entity, "数据不存在");
        T target = getById(targetId);
        AssertUtil.notNull(entity, "目标节点不存在");
        int oldLeftNo = entity.getLeftNo();
        int oldRightNo = entity.getRightNo();
        int targetLeftNo, targetRightNo;
        switch (relation) {
            case before: // 放在目标节点前面时
                targetLeftNo = target.getLeftNo();
                if (targetLeftNo < oldLeftNo) {
                    // 左移 newLeftNo = targetLeftNo
                    move(oldLeftNo, oldRightNo, targetLeftNo - oldLeftNo);
                } else {
                    // 右移 newRightNo = targetLeftNo - 1
                    move(oldLeftNo, oldRightNo, targetLeftNo - 1 - oldRightNo);
                }
                break;
            case after: // 放在目标节点后面时
                targetRightNo = target.getRightNo();
                if (targetRightNo < oldRightNo) {
                    // 左移 newLeftNo = targetRightNo + 1
                    move(oldLeftNo, oldRightNo, targetRightNo + 1 - oldLeftNo);
                } else {
                    // 右移 newRightNo = targetRightNo
                    move(oldLeftNo, oldRightNo, targetRightNo - oldRightNo);
                }
                break;
            default: // 放在目标节点下面时
                targetRightNo = target.getRightNo();
                if (targetRightNo < oldRightNo) {
                    // 左移 newLeftNo = targetRightNo
                    move(oldLeftNo, oldRightNo, targetRightNo - oldLeftNo);
                } else {
                    // 右移 newRightNo = targetRightNo - 1
                    move(oldLeftNo, oldRightNo, targetRightNo - 1 - oldRightNo);
                }
                break;
        }
    }

    @Override
    public void move(int oldLeftNo, int oldRightNo, int distance) {
        int diff = oldRightNo - oldLeftNo + 1;
        int newLeftNo = oldLeftNo + distance;
        int newRightNo = oldRightNo + distance;
        LambdaUpdateWrapper<T> updateWrapper1, updateWrapper2, updateWrapper3;
        if (distance < 0) {
            distance = -distance;
//            左移 oldLeftNo=6, oldRightNo=7, newLeftNo=2, newRightNo=3
//            UPDATE lg_auth_role set right_no=right_no + ${diff} WHERE right_no >= ${newLeftNo} and right_no < ${oldLeftNo};
//            UPDATE lg_auth_role set left_no=left_no - ${distance},right_no=right_no - ${distance} WHERE left_no >= ${oldLeftNo} and right_no <= ${oldRightNo};
//            UPDATE lg_auth_role set left_no=left_no + ${diff} WHERE right_no > ${newRightNo} and let_no >= ${newLeftNo} and left_no < ${oldLeftNo};
            // 更新其他节点右值(右移)
            updateWrapper1 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(RIGHT_PLUS_SQL + diff)
                    .ge(T::getRightNo, newLeftNo)
                    .lt(T::getRightNo, oldLeftNo);
            // 更新节点及其子节点(左移)
            updateWrapper2 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(LEFT_MINUS_SQL + distance)
                    .setSql(RIGHT_MINUS_SQL + distance)
                    .ge(T::getLeftNo, oldLeftNo)
                    .le(T::getRightNo, oldRightNo);
            // 更新其他节点左值(右移)
            updateWrapper3 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(LEFT_PLUS_SQL + diff)
                    .gt(T::getRightNo, newRightNo)
                    .ge(T::getLeftNo, newLeftNo)
                    .lt(T::getLeftNo, oldLeftNo);
        } else {
//            右移 oldLeftNo=2, oldRightNo=3, newLeftNo=6, newRightNo=7
//            UPDATE lg_auth_role set left_no=left_no - ${diff} WHERE left_no > ${oldRightNo} and left_no <= ${newRightNo};
//            UPDATE lg_auth_role set left_no=left_no + ${distance},right_no=right_no + ${distance} WHERE left_no >= ${oldLeftNo} and right_no <= ${oldRightNo};
//            UPDATE lg_auth_role set right_no=right_no - ${diff} WHERE left_no < ${newLeftNo} and right_no > ${oldRightNo} and right_no <= ${newRightNo};
            // 更新其他节点左值(左移)
            updateWrapper1 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(LEFT_MINUS_SQL + diff)
                    .gt(T::getLeftNo, oldRightNo)
                    .le(T::getLeftNo, newRightNo);
            // 更新节点及其子节点(右移)
            updateWrapper2 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(LEFT_PLUS_SQL + distance)
                    .setSql(RIGHT_PLUS_SQL + distance)
                    .ge(T::getLeftNo, oldLeftNo)
                    .le(T::getRightNo, oldRightNo);
            // 更新其他节点右值(左移)
            updateWrapper3 = Wrappers.<T>lambdaUpdate(getEntityClass())
                    .setSql(RIGHT_MINUS_SQL + diff)
                    .lt(T::getLeftNo, newLeftNo)
                    .gt(T::getRightNo, oldRightNo)
                    .le(T::getRightNo, newRightNo);
        }
        // 手动开启事务
        DefaultTransactionDefinition transDefinition = new DefaultTransactionDefinition();
        TransactionStatus transStatus = transactionManager.getTransaction(transDefinition);
        try {
            update(updateWrapper1);
            AssertUtil.isTrue(update(updateWrapper2), "节点更新失败");
            update(updateWrapper3);
            // 提交事务
            transactionManager.commit(transStatus);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(transStatus);
            throw e;
        }
    }
}
